package cn.demoncat.util.qr;

import cn.demoncat.util.exception.IoRuntimeException;
import cn.demoncat.util.io.CloseUtil;
import cn.demoncat.util.io.IoUtil;
import cn.demoncat.util.lang.ParamEmptyUtil;
import cn.demoncat.util.lang.constant.CharsetConstant;
import cn.demoncat.util.web.HttpClientUtil;
import cn.demoncat.util.web.ServletUtil;
import cn.demoncat.util.web.UrlUtil;
import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.client.j2se.MatrixToImageConfig;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import org.apache.commons.lang3.StringUtils;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;

/**
 * 二维码工具
 * 
 * @author 延晓磊
 *
 * @since 2016年10月31日
 */
public final class QrUtil {

	/**
	 * 生成并输出二维码图片
	 *
	 * @param text 		数据(字符串/url)
	 * @param param		二维码生成参数
	 * @param logo 		Logo图片的URL(GET请求)
	 * @param response
	 */
	public static void outQr(String text, QrParam param, String logo, HttpServletResponse response) {
		// 文本解码
		ParamEmptyUtil.checkBlank(text);
		text = UrlUtil.decode(text);
		// logo解码
		if (StringUtils.isNotBlank(logo)) {
			logo = UrlUtil.decode(logo);
		}
		// 输出
		outQr(text, param, logo, ServletUtil.getNocacheOut(response));
	}

	/**
	 * 生成并输出二维码图片
	 *
	 * @param text 			数据(字符串/url)
	 * @param param			二维码生成参数
	 *  @param logo 		Logo图片的URL(GET请求)
	 * @param os 			输出流(输出后将关闭)
	 */
	public static void outQr(String text, QrParam param, String logo, OutputStream os) {
		if (StringUtils.isNotBlank(logo)) {
			outLogoQr(text, param, logo, os);
		}else{
			outQr(text, param, os);
		}
	}

	/**
	 * 生成并输出二维码图片
	 * 
	 * @param text 			数据(字符串/url)
	 * @param param			二维码生成参数
	 * @param os 			输出流(输出后将关闭)
	 */
	public static void outQr(String text, QrParam param, OutputStream os) {
		// 参数校验
		ParamEmptyUtil.checkBlank(text);
		ParamEmptyUtil.checkNull(os);
		if (param == null) {
			param = new QrParam();
		}
		// 生成二维码
		try {
			// 生成二维码矩阵(根据数据和配置进行唯一标识)
			BitMatrix bitMatrix = new MultiFormatWriter().encode(text, BarcodeFormat.QR_CODE, param.getWidth(),param.getWidth(), getConfig(param.getMargin()));
			// 生成二维码图片(判断矩阵的XY轴，绘制黑色或白色)并输出
			MatrixToImageWriter.writeToStream(bitMatrix, QrConstant.IMAGE_FORMAT, os, new MatrixToImageConfig(param.getMasterColorNum(), param.getSlaveColorNum()));
		} catch (WriterException e) {
			throw new IoRuntimeException("生成二维码图片失败：" + e.getMessage(), e);
		} catch (IOException e) {
			throw new IoRuntimeException("输出二维码图片失败：" + e.getMessage(), e);
		} finally {
			CloseUtil.close(os);
		}
	}

	/**
	 * 生成并输出带LOGO的二维码图片
	 * 
	 * @param text 			数据(字符串/url)
	 * @param param			二维码生成参数
	 * @param logo 			Logo图片的URL(GET请求)
	 * @param os 			输出流(输出后将关闭)
	 */
	public static void outLogoQr(String text, QrParam param, String logo, OutputStream os) {
		outLogoQr(text, param, IoUtil.getInputStream(HttpClientUtil.doGetBytes(logo)), os);
	}

	/**
	 * 生成并输出带LOGO的二维码图片
	 * 
	 * @param text 			数据(字符串/url)
	 * @param param			二维码生成参数
	 * @param logo 			Logo图片输入流(读取后关闭)
	 * @param os 			输出流(输出后将关闭)
	 */
	public static void outLogoQr(String text, QrParam param, InputStream logo, OutputStream os) {
		// 参数校验
		ParamEmptyUtil.checkBlank(text);
		ParamEmptyUtil.checkNull(logo, os);
		if (param == null) {
			param = new QrParam();
		}
		// 生成二维码
		try {
			// 生成二维码矩阵(根据数据和配置进行唯一标识)
			BitMatrix bitMatrix = new MultiFormatWriter().encode(text, BarcodeFormat.QR_CODE, param.getWidth(), param.getWidth(), getConfig(param.getMargin()));
			// 生成二维码图片(判断矩阵的XY轴，绘制前景色或背景色色)
			BufferedImage qrImage = createQrImage(bitMatrix, param.getMasterColorNum(), param.getSlaveColorNum());
			// 插入Logo图片到二维码图片
			qrImage = insertLogoImage(qrImage, logo, param.getRatio(), param.getRadius(), param.getStroke());
			// 输出图片
			ImageIO.write(qrImage, QrConstant.IMAGE_FORMAT, os);
		} catch (WriterException e) {
			throw new IoRuntimeException("生成二维码图片失败：" + e.getMessage(), e);
		} catch (IOException e) {
			throw new IoRuntimeException("输出二维码图片失败：" + e.getMessage(), e);
		} finally {
			CloseUtil.close(logo);
			CloseUtil.close(os);
		}
	}

	/**
	 * 解析二维码图像
	 * 
	 * @param qr 	二维码(读取后将关闭流)
	 * @return	数据
	 */
	public static String getText(InputStream qr) {
		MultiFormatReader formatReader = new MultiFormatReader();
		try {
			// 读取二维码图片
			BufferedImage img = ImageIO.read(qr);
			// 将图像数据转换为 bit data
			BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(new BufferedImageLuminanceSource(img)));
			// 解析图像
			Result result = formatReader.decode(binaryBitmap, getConfig());
			return result.getText();
		} catch (IOException e) {
			throw new IoRuntimeException("读取二维码图片失败：" + e.getMessage(), e);
		} catch (NotFoundException e) {
			throw new IoRuntimeException("解析二维码图片失败：" + e.getMessage(), e);
		} finally {
			CloseUtil.close(qr);
		}
	}

	/**
	 * 获取生成二维码的配置
	 * 
	 * @param margin
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年10月31日
	 */
	private static Map<EncodeHintType, Object> getConfig(int margin) {
		Map<EncodeHintType, Object> map = new EnumMap<>(EncodeHintType.class);
		// 设置编码
		map.put(EncodeHintType.CHARACTER_SET, CharsetConstant.UTF8);
		// 设置容错级别：容错率和字节数，H最高
		map.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
		// 边框偏移
		map.put(EncodeHintType.MARGIN, margin);
		return map;
	}

	/**
	 * 获取解析二维码的配置
	 * 
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年10月31日
	 */
	private static Map<DecodeHintType, Object> getConfig() {
		Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
		List<BarcodeFormat> formats = new ArrayList<>();
		formats.add(BarcodeFormat.QR_CODE);
		hints.put(DecodeHintType.CHARACTER_SET, CharsetConstant.UTF8);
		hints.put(DecodeHintType.POSSIBLE_FORMATS, formats);
		return hints;
	}

	/**
	 * 绘制二维码图片
	 * 
	 * @param matrix			矩阵
	 * @param masterColor	二维码前景色
	 * @param slaveColor	二维码背景色
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年10月31日
	 */
	private static BufferedImage createQrImage(BitMatrix matrix, int masterColor, int slaveColor) {
		int width = matrix.getWidth();
		int height = matrix.getHeight();
		// 创建图片
		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		// 根据宽高绘制图片
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				// 用矩阵模式判断XY轴，选择绘制前景色还是背景色
				image.setRGB(x, y, matrix.get(x, y) ? masterColor : slaveColor);
			}
		}
		return image;
	}

	/**
	 * 插入Logo图片到二维码图片
	 *  
	 * @param qrImage	二维码
	 * @param logo 		LOGO(读取后关闭输入流)
	 * @param ratio		LOGO比例
	 * @param radius	LOGO圆角
	 * @param stroke	LOGO边框
	 * @return 
	 */
	private static BufferedImage insertLogoImage(BufferedImage qrImage, InputStream logo, double ratio, int radius,
			int stroke) {
		try {
			// 读取Logo图片，插入到二维码图片
			return insertLogoImage(qrImage, ImageIO.read(logo), ratio, radius, stroke);
		} catch (IOException e) {
			throw new IoRuntimeException("插入Logo到二维码图片失败：" + e.getMessage(), e);
		} finally {
			CloseUtil.close(logo);
		}
	}

	/**
	 * 插入Logo图片到二维码图片
	 *  
	 * @param qrImage	Qr图片
	 * @param logoImg	LOGO图片
	 * @param ratio		LOGO比例
	 * @param radius	LOGO圆角
	 * @param stroke	LOGO边框
	 * @return 
	 * @throws IOException 
	 * @throws FileNotFoundException 
	 */
	private static BufferedImage insertLogoImage(BufferedImage qrImage, Image logoImg, double ratio, int radius,
			int stroke) {
		// 缩放Logo图片(Logo图片通常为二维码图片的1/5)
		int qrWidth = qrImage.getWidth();
		int width = (int) (qrWidth * ratio);
		Image li = logoImg.getScaledInstance(width, width, BufferedImage.SCALE_SMOOTH);
		// 创建新Logo图片
		BufferedImage tag = new BufferedImage(width, width, BufferedImage.TYPE_INT_RGB);
		Graphics g = tag.getGraphics();
		g.drawImage(li, 0, 0, null);
		g.dispose();
		logoImg = li;
		// 插入Logo图片到二维码图片
		Graphics2D graph = qrImage.createGraphics();
		int x = (qrWidth - width) / 2;
		graph.drawImage(logoImg, x, x, width, width, null);
		// 绘制矩形边框
		Shape shape = new RoundRectangle2D.Float(x, x, width, width, radius, radius);
		graph.setStroke(new BasicStroke(stroke));
		graph.draw(shape);
		graph.dispose();
		return qrImage;
	}

	 
}
